package com.google.code.yargon.ui.lighting;

import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import java.util.Set;

/**
 * Intersection is when a (vision) ray intersects with a block. This class stores the block in question and contains the intersection points
 * and methods to determine field of vision.
 * 
 * @author Mika Myllynen
 *
 */
public class Intersection implements Comparable<Intersection> {
	
	// Is this intersection with the target block?
	private boolean onTarget = false;
	
	// Intersection points
	private Set<Point2D> points = null;
	
	private int x;
	private int y;
	
	/**
	 * Construct new intersection with a block at x,y (in block units)
	 * 
	 * @param x
	 * @param y
	 */
	public Intersection(int x, int y) {
		this.x = x;
		this.y = y;
	}
	
	/**
	 * Get intersection x-coordinate as integer (in block units)
	 * 
	 * @return
	 */
	public int getX() {
		return this.x;
	}
	
	/**
	 * Get intersection y-coordinate as integer (in block units)
	 * 
	 * @return
	 */
	public int getY() {
		return this.y;
	}
	
	/**
	 * Set this intersection to be with a target block
	 * 
	 */
	public void setOnTarget() {
		this.onTarget = true;
	}
	
	/**
	 * Return true if this intersection is with the target block
	 * 
	 * @return
	 */
	public boolean isOnTarget() {
		return this.onTarget;
	}
	
	/**
	 * Set all the intersection points within a block, relative to 0,0
	 * 
	 * @param points
	 */
	public void setPoints(Set<Point2D> points) {
		this.points = points;
	}
	
	/**
	 * Return all the intersection points within a block, relative to 0,0
	 * 
	 * @return
	 */
	public Set<Point2D> getPoints() {
		return this.points;
	}
	
	/**
	 * Get the nearest (to 0,0) intersection point within a block 
	 * 
	 * @return
	 */
	public Point2D getNearestPoint() {
		Point2D nearest = new Point2D.Double(1000, 1000);
		
		try { 
			for(Point2D p : points) {
				if(p.distance(0, 0) < nearest.distance(0, 0)) {
					nearest = p;
				}
			}
		} catch(Exception e) {
			// Ignore
		}
		return nearest;
	}
	
	public double getNearestPointDistance() {
		Point2D nearest = getNearestPoint();
		return nearest.distance(0, 0);
	}

	/**
	 * Implements compareTo, which makes it possible to sort an array of intersections.
	 * Sort is done by distance to the nearest intersection point within a block. If One intersection is with the target block, it is considered to be nearer
	 * in case the points are at same distance.
	 * 
	 */
	@Override
	public int compareTo(Intersection i2) {
		
		double d1 = getNearestPoint().distance(0, 0);
		double d2 = i2.getNearestPoint().distance(0, 0);
		
		if(d1 > d2) {
			return 1;
		} else if(d1 < d2) {
			return -1;
		} 
		
		// Intersections at same distance: intersection with target block considered closer
		else {
			if(this.isOnTarget()) {
				return -1;
			} else if(i2.isOnTarget()) {
				return 1;
			} else {
				return 0;
			}
		}
	}
	
	/**
	 * From http://stackoverflow.com/questions/5184815/java-intersection-point-of-a-polygon-and-line - adapted
	 * 
	 * @param block
	 * @param ray
	 * @return
	 */
	public static Set<Point2D> getIntersections(Rectangle2D block, Ray ray) {

        PathIterator pi = block.getPathIterator(null); 										// Getting an iterator along the polygon path
        double[] coords = new double[6]; 													// Double array with length 6 needed by iterator
        double[] firstCoords = new double[2]; 												// First point (needed for closing polygon path)
        double[] lastCoords = new double[2]; 												// Previously visited point
        
        Set<Point2D> intersections = new HashSet<Point2D>(); 								// List to hold found intersections
        
        Line2D.Double currentLine = null;
        
        pi.currentSegment(firstCoords); 													// Getting the first coordinate pair
        lastCoords[0] = firstCoords[0]; 													// Priming the previous coordinate pair
        lastCoords[1] = firstCoords[1];
   
        pi.next();
        while(! pi.isDone()) {
            final int type = pi.currentSegment(coords);
            
            switch(type) {
            
                case PathIterator.SEG_LINETO:
                    currentLine = new Line2D.Double(lastCoords[0], lastCoords[1], coords[0], coords[1]);
                    if(currentLine.intersectsLine(ray)) {
                        intersections.add(getIntersection(currentLine, ray));
                    }
                    lastCoords[0] = coords[0];
                    lastCoords[1] = coords[1];
                    break;
                    
                case PathIterator.SEG_CLOSE:
                    currentLine = new Line2D.Double(coords[0], coords[1], firstCoords[0], firstCoords[1]);
                    if(currentLine.intersectsLine(ray)) {
                        intersections.add(getIntersection(currentLine, ray));
                    }
                    break;
        
                default:
                	// Consider logging something... although this should not happen
                	break;
            }
            pi.next();
        }
        return intersections;
    }

	/**
	 * From http://stackoverflow.com/questions/5184815/java-intersection-point-of-a-polygon-and-line - adapted
	 * 
	 * @param line
	 * @param ray
	 * @return
	 */
    private static Point2D getIntersection(Line2D.Double line, Ray ray) {

        double x1 = line.getX1();
        double y1 = line.getY1();
        
        double x2 = line.getX2();
        double y2 = line.getY2();
        
        double x3 = ray.getX1();
        double y3 = ray.getY1();
        
        double x4 = ray.getX2();
        double y4 = ray.getY2();
          
        double x = ((x2 - x1)*(x3*y4 - x4*y3) - (x4 - x3)*(x1*y2 - x2*y1)) /
                   ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));
        
        double y = ((y3 - y4)*(x1*y2 - x2*y1) - (y1 - y2)*(x3*y4 - x4*y3)) /
                   ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));

        return new Point2D.Double(x, y);
    }	
}